#include "libcomm.h"
#if (COMM_FEATURE_LIBEVENT==1)

#include <event2/event.h>
#include <event2/http.h>
#include <event2/buffer.h>
#include <event2/util.h>
#include <event2/keyvalq_struct.h>
#include <event2/thread.h>

class RequestItem : public TaskItem
{
public:
	struct evhttp_request* m_req;
	HttpServer* m_server;
	RequestItem(struct evhttp_request* req, HttpServer* server):m_req(req),m_server(server)
	{
		//evhttp_request_own(m_req);
	}
	~RequestItem()
	{
		//evhttp_request_free(m_req);
	}
	virtual CommType Do()
	{
		const char *cmdtype;
		const char* uri;
		struct evkeyvalq *headers;

		uri = evhttp_request_get_uri(m_req);
		switch (evhttp_request_get_command(m_req)) 
		{
		case EVHTTP_REQ_GET: cmdtype = "GET"; break;
		case EVHTTP_REQ_POST: cmdtype = "POST"; break;
		case EVHTTP_REQ_HEAD: cmdtype = "HEAD"; break;
		case EVHTTP_REQ_PUT: cmdtype = "PUT"; break;
		case EVHTTP_REQ_DELETE: cmdtype = "DELETE"; break;
		case EVHTTP_REQ_OPTIONS: cmdtype = "OPTIONS"; break;
		case EVHTTP_REQ_TRACE: cmdtype = "TRACE"; break;
		case EVHTTP_REQ_CONNECT: cmdtype = "CONNECT"; break;
		case EVHTTP_REQ_PATCH: cmdtype = "PATCH"; break;
		default: cmdtype = "unknown"; break;
		}

		if(m_server->m_dump)
		{
			static unsigned int i = 0;
			++i;
			LOGINFO("%d. Received a \"%s\" request for \"%s\".", i, cmdtype, uri);
#if 1
			struct evkeyval *header;
			String headerInfo;
			headerInfo << "Headers:-------------------------\n";
			headers = evhttp_request_get_input_headers(m_req);
			for (header = headers->tqh_first; header;
				header = header->next.tqe_next) {
					headerInfo << header->key << ": " << header->value << "\n";
			}
			LOGINFO(headerInfo);
#endif
		}

		String request;
		{
			struct evbuffer *buf_input; 
			buf_input = evhttp_request_get_input_buffer(m_req);
			int content_length = evbuffer_get_length(buf_input);
			if(content_length > 0)
			{
				request.resize(content_length+1);
				evbuffer_remove(buf_input,(char*)request,content_length);
			}

			if(m_server->m_dump)
			{
				LOGINFO("Content:=========================\n%s",(char*)request);
			}
		}

		Buffer response;
		StringList responseHeaders;
		bool pageFound = m_server->m_handler->OnRequest(uri,cmdtype,request,responseHeaders,response);

		headers = evhttp_request_get_output_headers(m_req);
		{
			if(responseHeaders.count() == 0)
			{
				evhttp_add_header(headers, "Server", "CommonHttpServer");
				//evhttp_add_header(headers, "Connection", "keep-alive");
				//evhttp_add_header(headers, "Content-type", "text/html;charset=gb2312");
				//evhttp_add_header(headers, "Content-type", "text/plain;charset=utf-8");
				evhttp_add_header(headers, "Content-type", m_server->m_content_type);
				evhttp_add_header(headers, "Cache-Control", "no-cache");
			}
			else
			{
				for (int i=0;i<responseHeaders.count();i++)
				{
					StringList kvPair = String::split(responseHeaders[i],":");
					MYLIB_ASSERT(2==kvPair.count());
					evhttp_add_header(headers,kvPair[0],kvPair[1]);
				}
			}
		}

		struct evbuffer *buf_output;
		{
			buf_output = evhttp_request_get_output_buffer(m_req);
			if(pageFound)
			{
				evbuffer_add(buf_output, response.get(), response.size());
				evhttp_send_reply(m_req, 200, "OK", NULL);
			}
			else
			{
				evhttp_send_reply(m_req, 404, "not found", NULL);
			}
		}

		//Thread::Sleep(10000);
		return CommType::Null;
	}
};

HttpServer::HttpServer(HttpRequestHandler* handler,int listenPort,const char* listenIP,int timeoutSeconds,int threadCount,bool dump,const char* contentType,
					   const char* crtfile, const char* keyfile)
	:Thread("trd_httpserver_dispatch"),m_handler(handler),m_task(threadCount,"tsk_httpserver_request"),
	m_evbase(NULL),m_port(listenPort),m_ip(listenIP),m_timeout_seconds(timeoutSeconds),m_dump(dump),m_content_type(contentType),
	m_crtfile(crtfile),m_keyfile(keyfile)
{
	MYLIB_ASSERT_NOTNULL(m_handler);
#ifdef _WIN32
	WSADATA WSAData;
	WSAStartup(0x101, &WSAData);
#else
	MYLIB_ASSERT(SIG_ERR!=signal(SIGPIPE, SIG_IGN));
#endif

#ifdef _MSC_VER
	MYLIB_ASSERT(0==evthread_use_windows_threads());
#else
	MYLIB_ASSERT(0==evthread_use_pthreads());
#endif
}

HttpServer::~HttpServer(void)
{
	Stop();
	if(NULL != m_handler)
	{
		m_handler->Release();
		m_handler = NULL;
	}
}

bool HttpServer::Start()
{
	this->Run();
	return true;
}

void HttpServer::Stop()
{
	if(NULL == m_evbase)
		return;
	MYLIB_ASSERT(0==event_base_loopexit((struct event_base*)m_evbase,NULL));	
	this->Join();
	m_evbase = NULL;
}

#if (COMM_FEATURE_SSL==1)
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <event2/bufferevent_ssl.h>

static void SSLError(const char *func)
{
	LOGERR("%s failed.", func);

	/* This is the OpenSSL function that prints the contents of the
	 * error stack to the specified file handle. */
	ERR_print_errors_fp (stderr);
}

static SSL_CTX* SSLInit(const char* crtfile, const char* keyfile)
{
	SSL_CTX* ctx;

	SSL_library_init();
	OpenSSL_add_all_algorithms();
	ERR_load_crypto_strings();
	SSL_load_error_strings();
	if (!RAND_poll())
	{
		SSLError("RAND_poll");
		return NULL;
	}

	ctx = SSL_CTX_new(SSLv23_server_method());
	if (NULL == ctx) 
	{
		SSLError("SSL_CTX_new");
		return NULL;
	}

	if(NULL != keyfile && NULL != crtfile)
	{
		if (!SSL_CTX_use_certificate_file(ctx, crtfile, SSL_FILETYPE_PEM) ||
			!SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM)) 
		{
			SSLError("SSL_CTX_use_PrivateKey_file");
			SSL_CTX_free(ctx);
			return NULL;
		}
	}

	//SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2);
	return ctx;
}

/**
 * This callback is responsible for creating a new SSL connection
 * and wrapping it in an OpenSSL bufferevent.  This is the way
 * we implement an https server instead of a plain old http server.
 */
static struct bufferevent* bevcb (struct event_base *base, void *arg)
{ 
    struct bufferevent* r;
    SSL_CTX *ctx = (SSL_CTX *) arg;

    r = bufferevent_openssl_socket_new (base,
            -1,
            SSL_new (ctx),
            BUFFEREVENT_SSL_ACCEPTING,
            BEV_OPT_CLOSE_ON_FREE);
    return r;
}

#endif

static void request_cb(struct evhttp_request *req, void *arg)
{
	HttpServer* server = (HttpServer*)arg;
	RequestItem* item = new RequestItem(req,server);
	MYLIB_ASSERT_NOTNULL(item);
	server->m_task.Schedule(item);
}


int HttpServer::Process()
{
	struct event_base *base;
	struct evhttp *http;
	struct evhttp_bound_socket *handle;

	base = event_base_new();
	if (!base) {
		LOGERR("couldn't create an event_base: Exiting.");
		return 1;
	}

	http = evhttp_new(base);
	if (!http) {
		LOGERR("couldn't create evhttp. Exiting.");
		return 1;
	}

#if (COMM_FEATURE_SSL==1)
	SSL_CTX* server_ctx = NULL;
	server_ctx = SSLInit(m_crtfile,m_keyfile);
	MYLIB_ASSERT_NOTNULL(server_ctx);
	evhttp_set_bevcb (http, bevcb, server_ctx);
#endif

	evhttp_set_timeout(http, m_timeout_seconds);

	evhttp_set_gencb(http, request_cb, this);

	handle = evhttp_bind_socket_with_handle(http, m_ip, m_port);
	if (!handle) {
		LOGERR("couldn't bind to %s:%d. Exiting.",(char*)m_ip,m_port);
		return 1;
	}

	LOGINFO("http-server(%s:%d) is running...",(char*)m_ip,m_port);
	m_evbase = base;
	event_base_dispatch(base);

	return 0;
}

#endif